/*
 * Copyright 2010 WANdisco, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */
package com.googlecode.svnj.dav;

import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.stream.XMLStreamException;

import org.apache.jackrabbit.webdav.DavConstants;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavLocatorFactory;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.DavSessionProvider;
import org.apache.jackrabbit.webdav.MultiStatus;
import org.apache.jackrabbit.webdav.WebdavRequest;
import org.apache.jackrabbit.webdav.WebdavResponse;
import org.apache.jackrabbit.webdav.property.DavPropertyNameSet;
import org.apache.jackrabbit.webdav.server.AbstractWebdavServlet;
import org.apache.jackrabbit.webdav.version.DeltaVConstants;
import org.apache.jackrabbit.webdav.version.DeltaVResource;
import org.apache.jackrabbit.webdav.version.OptionsInfo;
import org.apache.jackrabbit.webdav.version.OptionsResponse;
import org.apache.jackrabbit.webdav.version.report.ReportInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.googlecode.svnj.RepoContext;
import com.googlecode.svnj.XMLUtil;
import com.googlecode.svnj.reports.ReportRequest;

public class SVNServlet extends AbstractWebdavServlet {

    private DavSessionProvider sessionProvider;
    private DavLocatorFactory locator;
    private DavResourceFactory resourceFactory;
    
    private static final Logger log = LoggerFactory.getLogger(SVNServlet.class);

    @Override
    public void init(ServletConfig conf) throws ServletException {
        super.init(conf);
        IO.get();
    }
    @Override
    protected void service(HttpServletRequest req, HttpServletResponse rsp)
    throws ServletException, IOException {
        RepoContext repo = null;
        long rev = -1;
        log.info("service : '" + req.getMethod () + " " + req.getRequestURI()
                + "' | pathInfo='" + req.getPathInfo() + "'");
        try {
            String revStr = req.getHeader(DeltaVConstants.HEADER_LABEL);
            if (revStr == null) {
                revStr = req.getParameter(SVNConstants.PARAM_WORKINGREV);
            }
            if (revStr != null)
                rev = Long.valueOf(revStr);
        } catch (Exception ignore) {}
        repo = RepoContext.findByPath(req.getContextPath() + req.getServletPath(),
                req.getPathInfo(), rev);
        if (repo == null) {
            log.error("no repo found at " + req.getPathInfo() + "");
            rsp.sendError(404);
            return;
        }
        try {
            IO.get().attachRepo(repo, req);
            super.service(req, rsp);
            
        } finally {
            repo.cleanupSelf();
        }
    }    
    @Override
    protected boolean execute(WebdavRequest request, WebdavResponse response,
            int method, DavResource resource)
    throws ServletException, IOException, DavException {
        /*log.info("executing \"" + request.getMethod() + " " + request.getRequestURI()
                + "\" on \"" + resource.getResourcePath() + "\"");*/
        try {
            return super.execute(request, response, method, resource);
        } catch (Exception e) {
            log.error("", e);
            response.sendXmlResponse(XMLUtil.encodeThrowable(e), WebdavResponse.SC_INTERNAL_SERVER_ERROR);
        }
        return false;
    }
    
    @Override
    public String getAuthenticateHeaderValue() {
        return AbstractWebdavServlet.DEFAULT_AUTHENTICATE_HEADER;
    }
    
    @Override
    public DavSessionProvider getDavSessionProvider() {
        if (sessionProvider == null) {
            sessionProvider = new SVNSessionProvider();
        }
        return sessionProvider;
    }

    @Override
    public DavLocatorFactory getLocatorFactory() {
        if (locator == null) {
            //log.info("constructing locator factory with prefix='" + prefix + "'");
            locator = new SVNLocatorFactory();
        }
        return locator;
    }

    @Override
    public DavResourceFactory getResourceFactory() {
        if (resourceFactory == null) {
            resourceFactory = new SVNResourceFactory();
        }
        return resourceFactory;
    }

    @Override
    protected boolean isPreconditionValid(WebdavRequest request,
            DavResource resource) {
        // FIXME: not sure if this is right
        return !resource.exists() || request.matchesIfHeader(resource);
    }

    @Override
    public void setDavSessionProvider(DavSessionProvider davSessionProvider) {
        this.sessionProvider = davSessionProvider;
    }

    @Override
    public void setLocatorFactory(DavLocatorFactory locatorFactory) {
        this.locator = locatorFactory;
    }

    @Override
    public void setResourceFactory(DavResourceFactory resourceFactory) {
        this.resourceFactory = resourceFactory;
    }

    @Override
    protected void doPropFind(WebdavRequest request, WebdavResponse response,
            DavResource resource) throws IOException, DavException {

        if (!resource.exists()) {
            response.sendError(DavServletResponse.SC_NOT_FOUND);
            return;
        }
        SVNResource svnRes = (SVNResource) resource;
        int depth = request.getDepth(DEPTH_INFINITY);
        DavPropertyNameSet requestProperties = request.getPropFindProperties();
        
        int propfindType = request.getPropFindType();
        log.info(
                "\n-------------PROPFIND request-------------\n"
                + XMLUtil.xmlToString(requestProperties, false)
                );
        if (svnRes.getSpecialResource().getType() != SpecialResource.Type.NOT_SPECIAL) {
            // FIXME: Pretty sure this is only an approximation of 'the right thing'
            // pore through httpd/mod_dav to figure exact semantics of this
            response.addHeader("Vary", DeltaVConstants.HEADER_LABEL);
        }
        MultiStatus mstatus = new MultiStatus();
        mstatus.addResourceProperties(resource, requestProperties,
                propfindType, depth);
        response.sendMultiStatus(mstatus);
        log.info(
                "\n-------------PROPFIND request-------------\n"
                + XMLUtil.xmlToString(requestProperties, true)
                + "-------------PROPFIND response------------\n" 
                + XMLUtil.xmlToString(mstatus, true)
                );
    }
    
    @Override
    protected void doReport(WebdavRequest req, WebdavResponse rsp,
            DavResource res)
    throws DavException, IOException {
        ReportInfo info = req.getReportInfo();
        /*final Namespace NS = SVNConstants.NS_SVN_DAV;
        // FIXME: hack to get the "<update-report>" node...
        Element elem = info.getContentElement("src-path", NS);
        if (elem == null) {
            throw new RuntimeException("no src-path in: ###"
                    + XMLUtil.xmlToString(info, true)
                    + "###");
        }
        Node n = elem.getParentNode();
        */
        // FIXME: hack to parse xml our own way, jackrabbit
        // ReportInfo won't let me get at node attributes, e.g.,
        // the rev="3" attribute of <S:entry> element...
        log.info("report name: '" + info.getReportName() + "'");
        String xml = XMLUtil.xmlToString(info, false);
        log.info("parsing report xml: ###" + xml + "###");
        ReportRequest rr = ReportRequest.parse(xml);
        try {
            rr.report(req, rsp, res);
        } catch (XMLStreamException xse) {
            throw new IOException(xse);
        }
    }

    @Override
    protected void doMkActivity(WebdavRequest req, WebdavResponse rsp, DavResource res)
    throws DavException, IOException {
        DavException de = new DavException(WebdavResponse.SC_CONFLICT, "SVNJ checkin not implemented (yet)");
        // throw de;
        rsp.sendXmlResponse(XMLUtil.encodeThrowable(de), de.getErrorCode());
        //rsp.sendXmlResponse(de, WebdavResponse.SC_NOT_IMPLEMENTED);
        //rsp.sendError(de);
        //rsp.setStatus(DavServletResponse.SC_INTERNAL_SERVER_ERROR);
        //rsp.getOutputStream().print("checkin (MKACTIVITY) currently not implemented in SVNJ");
    }    
    @Override
    protected void doOptions(WebdavRequest req, WebdavResponse rsp,
            DavResource resource) throws IOException, DavException {
        //rsp.addHeader("Server", "Jetty DAV/2 SVN/1.7.0");
        DateFormat dft = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z");
        dft.setTimeZone(TimeZone.getTimeZone("GMT"));
        rsp.addHeader("Date", dft.format(new Date()));
        rsp.addHeader("Server", "Apache/2.2.12 (Unix) DAV/2 SVN/1.7.0-dev");
        rsp.addHeader(DavConstants.HEADER_DAV, resource.getComplianceClass());

        RepoContext repo = IO.get().getRepo(req);
        addBoilerplateDAV(repo, rsp);
        
        rsp.addHeader("MS-Author-Via", DavConstants.HEADER_DAV);
        rsp.addHeader("Allow", resource.getSupportedMethods());
        addBoilerplateSVN(repo, rsp);
        
        /*
         * if (resource instanceof SearchResource) { String[] langs =
         * ((SearchResource) resource).getQueryGrammerSet()
         * .getQueryLanguages(); for (int i = 0; i < langs.length; i++) {
         * response.addHeader(SearchConstants.HEADER_DASL, "<" + langs[i] +
         * ">"); } }
         */

        // with DeltaV the OPTIONS request may contain a Xml body.
        OptionsResponse oR = null;
        OptionsInfo oInfo = req.getOptionsInfo();
        if (oInfo != null && resource instanceof DeltaVResource) {
            oR = ((DeltaVResource) resource).getOptionResponse(oInfo);
        }
        log.info(
                "\n-------------OPTIONS request-------------\n"
                + XMLUtil.xmlToString(oInfo, true) +
                "-------------OPTIONS response------------\n" 
                + XMLUtil.xmlToString(oR, true)
                );
        /*
        String canned = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
        + "<D:options-response xmlns:D=\"DAV:\">\n"
        + "<D:activity-collection-set><D:href>/svn/alphabet/!svn/act/</D:href>"
        + "</D:activity-collection-set></D:options-response>";
        rsp.setContentLength(canned.length());
        rsp.addHeader("Connection", "Keep-Alive");
        rsp.addHeader("Content-Type", "text/xml; charset=\"utf-8\"");
        rsp.getOutputStream().print(canned);
        */
        if (oR == null) {
            rsp.setStatus(DavServletResponse.SC_OK);
        } else {
            rsp.sendXmlResponse(oR, DavServletResponse.SC_OK);
        }
    }
    
    /* boilerplate DAV headers for SVN 1.7 */
    static final String[] BOILERPLATE = { 
        "version-control,checkout,working-resource",
        "merge,baseline,activity,version-controlled-collection",
        "http://subversion.tigris.org/xmlns/dav/svn/depth",
        "http://subversion.tigris.org/xmlns/dav/svn/log-revprops",
        "http://subversion.tigris.org/xmlns/dav/svn/partial-replay",
        "http://subversion.tigris.org/xmlns/dav/svn/mergeinfo",
        "<http://apache.org/dav/propset/fs/1>"
    };

    protected void addBoilerplateDAV(RepoContext repo, WebdavResponse rsp) {
        for (String h : BOILERPLATE) {
            rsp.addHeader("DAV", h);
        }
    }
    protected void addBoilerplateSVN(RepoContext repo, WebdavResponse rsp) {
        /*
         SVN-Youngest-Rev: 2\r\n
         SVN-Repository-UUID: b18a00d4-f7e0-44e2-a8db-d2767b44ec5c\r\n
         SVN-Repository-Root: /svn/alphabet */
        rsp.addHeader("SVN-Youngest-Rev", String.valueOf(repo.getYoungestRev()));
        rsp.addHeader("SVN-Repository-UUID", repo.getUuid());
        rsp.addHeader("SVN-Repository-Root", repo.getContextPath());
        /*
        SVN-Me-Resource: /svn/alphabet/!svn/me\r\n
        SVN-Rev-Root-Stub: /svn/alphabet/!svn/rvr\r\n
        SVN-Rev-Stub: /svn/alphabet/!svn/rev\r\n
        SVN-Txn-Root-Stub: /svn/alphabet/!svn/txr\r\n
        SVN-Txn-Stub: /svn/alphabet/!svn/txn     */
        rsp.addHeader("SVN-Me-Resource", repo.getMeResource());
        rsp.addHeader("SVN-Rev-Root-Stub", repo.getRevRootStub());
        rsp.addHeader("SVN-Rev-Stub", repo.getRevStub());
        rsp.addHeader("SVN-Txn-Root-Stub", repo.getTxnRootStub());
        rsp.addHeader("SVN-Txn-Stub", repo.getTxnStub());
        
    }
    
}
